import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.jar.*;
import java.util.zip.ZipEntry;

public class UniversalFatJarBuilder {

    private static final List<String> DEFAULT_EXCLUDE_PATTERNS = Arrays.asList(
        "META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA", "META-INF/*.EC",
        "META-INF/MANIFEST.MF"
    );
    
    private final Set<String> addedEntries = new HashSet<>();
    // Map键唯一：确保同一接口（键）只存储一组实现类（值）
    private final Map<String, Set<String>> springFactoriesContent = new HashMap<>();

    public void build(String outputJarPath, String mainClass, 
                     List<File> classDirectories, List<File> jarFiles) throws IOException {
        build(outputJarPath, mainClass, classDirectories, jarFiles, null);
    }

    public void build(String outputJarPath, String mainClass, 
                     List<File> classDirectories, List<File> jarFiles,
                     List<String> customExcludePatterns) throws IOException {
        
        File outputJar = new File(outputJarPath);
        File parentDir = outputJar.getParentFile();
        if (parentDir != null && !parentDir.exists()) {
            if (!parentDir.mkdirs()) {
                throw new IOException("创建输出目录失败: " + parentDir.getAbsolutePath());
            }
        }

        Manifest manifest = createManifest(mainClass);
        
        try (JarOutputStream jarOut = new JarOutputStream(
                new FileOutputStream(outputJar), manifest)) {
            
            // 1. 收集所有spring.factories（Map自动去重键）
            collectSpringFactories(jarFiles, customExcludePatterns);
            // 2. 添加class和依赖JAR
            addClassDirectories(classDirectories, jarOut, customExcludePatterns);
            addDependencyJars(jarFiles, jarOut, customExcludePatterns);
            // 3. 写入合并后的spring.factories（同一键只写一行）
            writeMergedSpringFactories(jarOut);
            
        }
        
        System.out.println("✅ FatJAR构建成功: " + outputJar.getAbsolutePath());
    }

    private Manifest createManifest(String mainClass) {
        Manifest manifest = new Manifest();
        Attributes mainAttrs = manifest.getMainAttributes();
        mainAttrs.put(Attributes.Name.MANIFEST_VERSION, "1.0");
        mainAttrs.put(Attributes.Name.MAIN_CLASS, mainClass);
        mainAttrs.put(Attributes.Name.CLASS_PATH, ".");
        return manifest;
    }

    private void collectSpringFactories(List<File> jarFiles, List<String> customExcludePatterns) throws IOException {
        for (File jarFile : jarFiles) {
            if (!jarFile.exists() || !jarFile.isFile() || !jarFile.getName().endsWith(".jar")) {
                continue;
            }
            
            try (JarInputStream jarIn = new JarInputStream(new FileInputStream(jarFile))) {
                JarEntry entry;
                while ((entry = jarIn.getNextJarEntry()) != null) {
                    String entryName = entry.getName();
                    
                    if ("META-INF/spring.factories".equals(entryName) && !shouldExclude(entryName, customExcludePatterns)) {
                        BufferedReader reader = new BufferedReader(
                            new InputStreamReader(jarIn, StandardCharsets.UTF_8));
                        String line;
                        StringBuilder currentLine = new StringBuilder();
                        
                        // 处理多行拼接（\结尾）
                        while ((line = reader.readLine()) != null) {
                            line = line.trim();
                            if (line.isEmpty() || line.startsWith("#")) {
                                continue;
                            }
                            
                            if (line.endsWith("\\")) {
                                currentLine.append(line.substring(0, line.length() - 1).trim());
                            } else {
                                currentLine.append(line);
                                processSpringFactoriesLine(currentLine.toString());
                                currentLine.setLength(0); // 重置缓冲区，避免残留
                            }
                        }
                    }
                    jarIn.closeEntry();
                }
            }
        }
    }

    private void processSpringFactoriesLine(String line) {
        int eqIndex = line.indexOf("=");
        if (eqIndex == -1 || eqIndex == line.length() - 1) {
            return; // 过滤无值或格式错误的行
        }
        
        String key = line.substring(0, eqIndex).trim();
        String valuePart = line.substring(eqIndex + 1).trim();
        if (valuePart.isEmpty()) {
            return; // 过滤值为空的行
        }
        
        // 分割值（处理空格，去重）
        String[] values = valuePart.split("\\s*,\\s*");
        for (String value : values) {
            value = value.trim();
            // 过滤无效类名（必须含包名，不含=）
            if (!value.isEmpty() && value.contains(".") && !value.contains("=")) {
                springFactoriesContent.computeIfAbsent(key, k -> new LinkedHashSet<>())
                                     .add(value);
            }
        }
    }

    private void writeMergedSpringFactories(JarOutputStream jarOut) throws IOException {
        if (springFactoriesContent.isEmpty()) {
            return;
        }
        
        JarEntry entry = new JarEntry("META-INF/spring.factories");
        jarOut.putNextEntry(entry);
        
        BufferedWriter writer = new BufferedWriter(
            new OutputStreamWriter(jarOut, StandardCharsets.UTF_8));
        
        // 遍历Map（键唯一），每个键只写一行
        for (Map.Entry<String, Set<String>> entrySet : springFactoriesContent.entrySet()) {
            String key = entrySet.getKey();
            Set<String> values = entrySet.getValue();
            if (values.isEmpty()) {
                continue; // 跳过无值的键
            }
            
            // 拼接值（每3个值换行，带\，符合规范）
            StringBuilder valueStr = new StringBuilder();
            int count = 0;
            for (String value : values) {
                if (count > 0) {
                    valueStr.append(",");
                    if (count % 3 == 0) {
                        valueStr.append("\n  "); // 缩进，美观且符合规范
                    }
                }
                valueStr.append(value);
                count++;
            }
            
            // 同一键只写一行
            writer.write(key + "=" + valueStr);
            writer.newLine(); // 换行，分隔不同键
        }
        
        writer.flush(); // 确保内容全部写入
        jarOut.closeEntry();
        addedEntries.add("META-INF/spring.factories");
    }

    // 以下方法保持不变
    private void addClassDirectories(List<File> classDirectories, JarOutputStream jarOut, 
                                  List<String> customExcludePatterns) throws IOException {
        for (File classDir : classDirectories) {
            if (classDir.exists() && classDir.isDirectory()) {
                addDirectoryRecursive(classDir, classDir, jarOut, customExcludePatterns);
            }
        }
    }

    private void addDirectoryRecursive(File baseDir, File currentDir, JarOutputStream jarOut, 
                                      List<String> customExcludePatterns) throws IOException {
        File[] files = currentDir.listFiles();
        if (files == null) return;
        
        for (File file : files) {
            if (file.isDirectory()) {
                addDirectoryRecursive(baseDir, file, jarOut, customExcludePatterns);
            } else {
                String entryName = baseDir.toPath()
                        .relativize(file.toPath())
                        .toString()
                        .replace(File.separatorChar, '/');
                
                if (!shouldExclude(entryName, customExcludePatterns) && !addedEntries.contains(entryName)) {
                    addFileToJar(file, entryName, jarOut);
                }
            }
        }
    }

    private void addDependencyJars(List<File> jarFiles, JarOutputStream jarOut, 
                           List<String> customExcludePatterns) throws IOException {
        for (File jarFile : jarFiles) {
            if (jarFile.exists() && jarFile.isFile() && jarFile.getName().endsWith(".jar")) {
                addJarContent(jarFile, jarOut, customExcludePatterns);
            }
        }
    }

    private void addJarContent(File jarFile, JarOutputStream jarOut, List<String> customExcludePatterns) throws IOException {
        try (JarInputStream jarIn = new JarInputStream(new FileInputStream(jarFile))) {
            JarEntry entry;
            while ((entry = jarIn.getNextJarEntry()) != null) {
                String entryName = entry.getName();
                
                if (entry.isDirectory() 
                    || addedEntries.contains(entryName) 
                    || shouldExclude(entryName, customExcludePatterns)
                    || "META-INF/spring.factories".equals(entryName)) {
                    jarIn.closeEntry();
                    continue;
                }
                
                JarEntry newEntry = new JarEntry(entryName);
                newEntry.setTime(entry.getTime());
                jarOut.putNextEntry(newEntry);
                
                byte[] buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = jarIn.read(buffer)) != -1) {
                    jarOut.write(buffer, 0, bytesRead);
                }
                
                jarOut.closeEntry();
                jarIn.closeEntry();
                addedEntries.add(entryName);
            }
        }
    }

    private void addFileToJar(File file, String entryName, JarOutputStream jarOut) throws IOException {
        JarEntry entry = new JarEntry(entryName);
        entry.setTime(file.lastModified());
        jarOut.putNextEntry(entry);
        
        try (FileInputStream fis = new FileInputStream(file)) {
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                jarOut.write(buffer, 0, bytesRead);
            }
        }
        
        jarOut.closeEntry();
        addedEntries.add(entryName);
    }

    private boolean shouldExclude(String entryName, List<String> customExcludePatterns) {
        for (String pattern : DEFAULT_EXCLUDE_PATTERNS) {
            if (matchesPattern(entryName, pattern)) {
                return true;
            }
        }
        
        if (customExcludePatterns != null) {
            for (String pattern : customExcludePatterns) {
                if (matchesPattern(entryName, pattern)) {
                    return true;
                }
            }
        }
        
        return false;
    }

    private boolean matchesPattern(String entryName, String pattern) {
        String regex = pattern.replace(".", "\\.").replace("*", ".*");
        return entryName.matches(regex);
    }
}